package com.baiyue.security;

import com.baiyue.utils.SecurityUtil;
import org.apache.commons.codec.binary.Base32;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

/**
 * 两步验证器
 * 每30秒，上下差10秒验证密钥
 *
 * @author tangfan 2018/8/31 12:22
 */
public class JunboAuthenticator {
    /**
     * 最多可偏移时间长度 单位：秒
     */
    private static int windowSize = 10;

    private static void setWindowSize(int s) {
        if (s >= 1 && s <= 17) {
            windowSize = s;
        }
    }

    private static String getQRBarcodeURL(String user, String host, String secret) {
        String format = "https://www.google.com/chart?chs=200x200&chld=M%%7C0&cht=qr&chl=otpauth://totp/%s@%s%%3Fsecret%%3D%s";
        return String.format(format, user, host, secret);
    }

    private static boolean checkCode(String secret, long code, long timeMsec) throws InvalidKeyException, NoSuchAlgorithmException {
        Base32 codec = new Base32();
        byte[] decodedKey = codec.decode(secret);
        long t = (timeMsec / 1000L) / 30L;

        for (int i = -windowSize; i <= windowSize; i++) {
            long hash;
            hash = verifyCode(decodedKey, t + i);
            if (hash == code) {
                return true;
            }
        }
        return false;
    }

    private static int verifyCode(byte[] key, long t) throws NoSuchAlgorithmException, InvalidKeyException {
        byte[] data = new byte[8];
        long value = t;
        for (int i = 8; i-- > 0; value >>>= 8) {
            data[i] = (byte) value;
        }

        SecretKeySpec signKey = new SecretKeySpec(key, "HmacSHA1");
        Mac mac = Mac.getInstance("HmacSHA1");
        mac.init(signKey);
        byte[] hash = mac.doFinal(data);
        int offset = hash[20 - 1] & 0xF;
        long truncatedHash = 0;
        for (int i = 0; i < 4; i++) {
            truncatedHash <<= 8;
            truncatedHash |= (hash[offset + i] & 0xFF);
        }
        truncatedHash &= 0x7FFFFFFF;
        truncatedHash %= 1000000;
        return (int) truncatedHash;
    }

    /**
     * 生成一个验证密钥
     *
     * @return
     */
    public static String generateSecret(String user, String host) {
        String secret = "";
        try {
            secret = SecurityUtil.generateSecretKey();
            String url = JunboAuthenticator.getQRBarcodeURL(user, host, secret);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return secret;
    }

    /**
     * 根据密钥获取验证码
     *
     * @param savedSecret 密钥
     * @return 验证码 -1时表示未获取成功
     */
    public static int getAuthCode(String savedSecret) {
        Base32 codec = new Base32();
        byte[] decodedKey = codec.decode(savedSecret);
        try {
            long t = (System.currentTimeMillis() / 1000L) / 30L;
            return verifyCode(decodedKey, t);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
        }
        return -1;
    }

    /**
     * 检查代码是否合法
     *
     * @param userCode    用户验证码
     * @param savedSecret 密钥
     * @return true 成功
     */
    public static boolean checkAuthCode(String userCode, String savedSecret) {
        long code = Long.parseLong(userCode);
        long t = System.currentTimeMillis();
        setWindowSize(3);
        boolean r = false;
        try {
            r = checkCode(savedSecret, code, t);
        } catch (InvalidKeyException | NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return r;
    }
}
